Executable code for a computer may, as part of its runtime operation, carry out self-modifying operations. One common example of such self-modifying executable code is where executable code contains unresolved data or procedure references. This will occur, for example, where Java language code is dynamically compiled to produce code that calls methods and refers to data that is unresolved at compile time. One approach to permit the compiled code to be executed correctly is to resolve the previously unresolved references at the time that the code is first executed. The compiler generates executable code for calling a routine, sometimes referred to as helper code, which carries out reference resolution at runtime. The helper code is typically provided with the compiler.
When, during execution, the unresolved reference is reached, the helper code is provided with data identifying the reference to be resolved and is then executed. The helper code carries out instructions to resolve the previously unresolved method call or data references in the code. The helper code modifies the executable code by overwriting the unresolved references with the proper values which the helper code has determined at execution time. This ensures that the runtime resolution of the reference occurs only once. The code generated by such a compiler is referred to as “self-modifying” because the code's call to the helper code results in the replacement of a portion of the executable code (originally containing the unresolved references) with modified code which has resolved references.
In multiprocessor computers, self-modification of code at execution time may occur for the above, or other reasons. Such self-modification may create errors if different threads or processes execute a section of code which is in the process of being modified by another process or thread. To prevent this potential problem (race condition), different solutions have been proposed in the prior art. One approach is to implement a global locking arrangement. This requires a process or thread to obtain a global lock to modify a given section of code. The use of a global lock prevents multiple threads from executing the code to be modified, but there is a significant overhead involved as the processes or threads waiting on the global lock are unable to carry out other processing that may be unrelated to the code being modified.
Other solutions have been devised which rely on local lock arrangements. For example, a byte or word may be reserved for each code site being modified. Threads will lock on the byte or word for the code site. This approach has a cost in the code space required to implement the specific locks for each code site. This approach must also include code to avoid the potential race condition where a processor is executing in the area of code being modified at the time that the code is being updated, but before the processor reaches code to determine whether the lock is available or not.
Another solution is to add control flow to the section of code being modified to protect the code by preventing access to the code by other threads until the code has been modified. This approach will leave orphaned control flow statements in the executable code even after the modification of the code has taken place.
It is also possible to add a level of indirection in the code which calls the routine (the helper code) that resolves the reference. A locking mechanism may be more readily implemented for such a calling arrangement but the resulting code will be slower than otherwise due to the introduction of the additional level of indirection.
It is therefore desirable to have a mechanism for locking in a multi-processor thread-safe environment to permit the runtime modification in self-modifying code such that the resulting code is efficient and potential race conditions are eliminated.